/*************************************************************************
  > Filename   : capapi.c
  > Author     : oneface - one_face@sina.com
  > Company    : 一尊还酹江月
  > Time       : 2018-02-27 13:36:17
 ************************************************************************/

#include <tinyalsa/asoundlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <stdint.h>
#include <signal.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <cutils/android_reboot.h>
#include <sys/types.h>
#include <dirent.h>
#include "cutils/log.h"
#include "capapi.h"

int capturing = 1;

void sigtm_handler(int sig)
{
	ALOGD("hello, timeuse over !\n");
	capturing = 0;
}

static int check_file_exists(char *path)
{
	int ret = -1;

	ALOGD("\n");
	if (path == NULL) {
		ALOGD("check file is not exsit\n");
		return -1;
	}

	ret = access(path, F_OK);

	return ret;
}

static void wait_sdcard_mount(char *path)
{
	int retry = 1;

	/* when reboot, fuse service may be start later,  wait it a moment! */
	while (retry++) {
		if (check_file_exists(path) != 0) {
			ALOGE(" '%s' is not exsit, retry %d\n", path, retry);
			if (retry == 60) {
				exit(-1);
			}
			sleep(1);
		} else {
			ALOGD(" '%s' is exsit\n", path);
			break;
		}
	}
}

static int rm_dir(const char *path)
{
	struct dirent *dir;
	struct stat st;
	char sub_path[64];

	DIR *dirp = opendir(path);
	if (!dirp) {
		return -1;
	}

	while ((dir = readdir(dirp)) != NULL) {
		if (strcmp(dir->d_name, ".") == 0
		    || strcmp(dir->d_name, "..") == 0) {
			continue;
		}
		sprintf(sub_path, "%s/%s", path, dir->d_name);
		if (lstat(sub_path, &st) == -1) {
			continue;
		}
		if (S_ISDIR(st.st_mode)) {
			if (rm_dir(sub_path) == -1) {
				closedir(dirp);
				return -1;
			}
			rmdir(sub_path);
		} else if (S_ISREG(st.st_mode)) {
			unlink(sub_path);
		} else {
			continue;
		}
	}
	if (rmdir(path) == -1) {
		closedir(dirp);
		return -1;
	}

	closedir(dirp);
	return 0;
}

static void setup_report_dir()
{
	ALOGD("\n");
	wait_sdcard_mount("/sdcard");
	if (check_file_exists(PCM_TEST) == 0) {
		ALOGD("check file is exsit, remove this dir\n");
		rm_dir(PCM_TEST);
	}

	/* first boot creat a new test dir */
	mkdir(PCM_TEST, 0755);
}

unsigned int capture_sample(FILE * file, unsigned int card, unsigned int device,
			    unsigned int channels, unsigned int rate,
			    enum pcm_format format, unsigned int period_size,
			    unsigned int period_count)
{
	struct pcm_config config;
	struct pcm *pcm;
	char *buffer;
	unsigned int size;
	unsigned int bytes_read = 0;

	config.channels = channels;
	config.rate = rate;
	config.period_size = period_size;
	config.period_count = period_count;
	config.format = format;
	config.start_threshold = 0;
	config.stop_threshold = 0;
	config.silence_threshold = 0;

	pcm = pcm_open(card, device, PCM_IN, &config);
	if (!pcm || !pcm_is_ready(pcm)) {
		fprintf(stderr, "Unable to open PCM device (%s)\n",
			pcm_get_error(pcm));
		return 0;
	}

	size = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm));
	buffer = malloc(size);
	if (!buffer) {
		fprintf(stderr, "Unable to allocate %d bytes\n", size);
		free(buffer);
		pcm_close(pcm);
		return 0;
	}

	ALOGD("Capturing sample: %u ch, %u hz, %u bit\n", channels, rate,
	      pcm_format_to_bits(format));

	while (capturing && !pcm_read_ex(pcm, buffer, size)) {
		if (fwrite(buffer, 1, size, file) != size) {
			fprintf(stderr, "Error capturing sample\n");
			break;
		}
		bytes_read += size;
	}

	free(buffer);
	pcm_close(pcm);
	return pcm_bytes_to_frames(pcm, bytes_read);
}

static int capture_wav(command_args cmdargs, char *repeat_idx)
{
	ALOGD("capture wav ...\n");
	struct wav_header header;
	FILE *file;
	unsigned int frames;
	enum pcm_format format;

	char path[256] = { 0 };
	char *file_fmt = ".wav";

	/* from user args wrapper */
	unsigned int card = cmdargs.card;
	unsigned int device = cmdargs.device;
	unsigned int channels = cmdargs.channels;
	unsigned int rate = cmdargs.rate;
	unsigned int bits = cmdargs.bits;
	unsigned int period_size = cmdargs.period_size;
	unsigned int period_count = cmdargs.period_count;

	/* header wrapper */
	header.riff_id = ID_RIFF;
	header.riff_sz = 0;
	header.riff_fmt = ID_WAVE;
	header.fmt_id = ID_FMT;
	header.fmt_sz = 16;
	header.audio_format = FORMAT_PCM;
	header.num_channels = channels;
	header.sample_rate = rate;

	sprintf(path, "%s%s", repeat_idx, file_fmt);
	file = fopen(path, "wb+");
	if (!file) {
		fprintf(stderr, "Unable to create file '%s'\n", path);
		return -1;
	}

	switch (bits) {
	case 32:
		format = PCM_FORMAT_S32_LE;
		break;
	case 24:
		format = PCM_FORMAT_S24_LE;
		break;
	case 16:
		format = PCM_FORMAT_S16_LE;
		break;
	default:
		fprintf(stderr, "%d bits is not supported.\n", bits);
		return 1;
	}

	header.bits_per_sample = pcm_format_to_bits(format);
	header.byte_rate = (header.bits_per_sample / 8) * channels * rate;
	header.block_align = channels * (header.bits_per_sample / 8);
	header.data_id = ID_DATA;

	/* leave enough room for header */
	fseek(file, sizeof(struct wav_header), SEEK_SET);

	frames = capture_sample(file, card, device, header.num_channels,
				header.sample_rate, format,
				period_size, period_count);
	ALOGD("Captured %d frames\n", frames);

	/* write header now all information is known */
	header.data_sz = frames * header.block_align;
	header.riff_sz = header.data_sz + sizeof(header) - 8;
	fseek(file, 0, SEEK_SET);
	fwrite(&header, sizeof(struct wav_header), 1, file);
	fclose(file);
	return 0;
}

static int parse_wavheader(FILE * file,
			   struct riff_wave_header *riff_wave_header,
			   struct chunk_header *chunk_header,
			   struct chunk_fmt *chunk_fmt)
{
	int more_chunks = 1;

	fread(riff_wave_header, sizeof(*riff_wave_header), 1, file);
	if ((riff_wave_header->riff_id != ID_RIFF) ||
	    (riff_wave_header->wave_id != ID_WAVE)) {
		ALOGE("Error: this is not a riff/wave file\n");
		fclose(file);
		return -1;
	}
	//Note: please, stop to think what`s the chunks ...
	do {
		fread(chunk_header, sizeof(*chunk_header), 1, file);

		switch (chunk_header->id) {
		case ID_FMT:
			fread(chunk_fmt, sizeof(*chunk_fmt), 1, file);
			/* If the format header is larger, skip the rest */
			if (chunk_header->sz > sizeof(*chunk_fmt))
				fseek(file,
				      chunk_header->sz - sizeof(*chunk_fmt),
				      SEEK_CUR);
			break;
		case ID_DATA:
			/* Stop looking for chunks */
			more_chunks = 0;
			break;
		default:
			/* Unknown chunk, skip bytes */
			fseek(file, chunk_header->sz, SEEK_CUR);
		}
	} while (more_chunks);

	//ALOGE("wav header parse over chunk_fmt.bits_per_sample=%u\n", chunk_fmt->bits_per_sample);
	return 0;
}

/*
ret:
0:success -1:check error

 */
static int check_ffffff(FILE * file, uint16_t bits)
{
	int ret = 0;
	uint32_t *sample_24;
	uint16_t *sample_16;
	char *buffer = NULL;

	buffer = (char *)malloc(sizeof(char) * bits / 8);
	if (!buffer) {
		ALOGE("buffer malloc failed\n");
		return -1;
	}
	while ((ret = fread(buffer, 1, sizeof(char) * bits / 8, file)) > 0) {
		//ALOGE("%x %x %x %x\n", buffer[0], buffer[1], buffer[2], buffer[3]);
		if (bits == 32) {
			sample_24 = (uint32_t *) buffer;
			//ALOGE("%04x\n", *sample_24);
			if (*sample_24 == 0xffffff00) {
				ALOGE("24bits find 0xffffff00 bug\n");
				ret = -1;
				break;
			}
		} else if (bits == 16) {
			sample_16 = (uint16_t *) buffer;
			if (*sample_16 == 0xffff) {
				ALOGE("16bits find 0xffff bug\n");
				ret = -1;
				break;
			}
		} else {
			ALOGE("bits %u is invalid\n", bits);
			ret = -1;
			break;
		}
	}

	free(buffer);
	buffer = NULL;

	return ret;
}

static int analyze_capdata(unsigned int repeat)
{
	unsigned int i;
	int ret = -1;
	FILE *file;
	char wavfile[256];
	struct riff_wave_header riff_wave_header;
	struct chunk_header chunk_header;
	struct chunk_fmt chunk_fmt;
	uint32_t *per_sample, *data_tmp;

	//for open /sdcard/pcm-test/repeat_000x.wav
	for (i = 1; i < repeat; i++) {
		sprintf(wavfile, "%s/repeat_000%d.wav", PCM_TEST, i);
		file = fopen(wavfile, "rb");
		if (!file) {
			ALOGE("Unable to open file '%s'\n", wavfile);
			return -1;
		}
		//check ff ff ff
		ret =
		    parse_wavheader(file, &riff_wave_header,
				    &chunk_header, &chunk_fmt);
		if (ret == -1) {
			return -1;
		}

		ALOGE("bits =%u\n", chunk_fmt.bits_per_sample);
		ret = check_ffffff(file, chunk_fmt.bits_per_sample);
		fclose(file);
		if (ret == -1) {
			ALOGE("The file '%s' has [ff bug] or no data\n",
			       wavfile);
			return -1;
		}
		ALOGE("The file '%s' is OK\n", wavfile);
	}

	return 0;
}

int record_work(command_args cmdargs)
{
	int ret = -1;
	char repeat_idx[128];
	unsigned int repeat = 1;

	setup_report_dir();

	signal(SIGALRM, sigtm_handler);
	alarm(cmdargs.time);

	while (1) {
		sprintf(repeat_idx, "%s/repeat_%04d", PCM_TEST, repeat);
		ret = capture_wav(cmdargs, repeat_idx);
		if (ret == -1) {
			ALOGE("capture failed\n");
			return -1;
		}

		/*here alarm is over, check repeat */
		if (repeat < cmdargs.repeat) {
			alarm(cmdargs.time);
			capturing = 1;
			repeat++;
		} else {
			ALOGD("record work all repeat is over\n");
			break;
		}
	}

	ret = analyze_capdata(repeat);
	if (ret == -1) {
		ALOGE("analyze capdata: some wav file has [Bug 'ff']\n");
		return -1;
	}

	return 0;
}
